from zope.interface import implements
from tempfile import mkdtemp
import shutil
import os, sys

APPLICATION_INSTALL_DIR = os.environ.get('SIMPLEWATCHER_INSTALL')
if not APPLICATION_INSTALL_DIR:
    raise EnvironmentError("must set environment variable SIMPLEWATCHER_INSTALL")
sys.path.append('%s/lib' % APPLICATION_INSTALL_DIR)

from watcher.logger import Logger
from watcher.exceptions import *
from watcher import WatcherContainer
from watcher.interfaces import IBranch, IExecutePath, IWatch
from watcher.interfaces import IAction, IDecision
from watcher.action import Action
from watcher.decision import Decision


class Transaction(object):
    """transaction for actions"""
    def __init__(self, actions, logger):
        self.actions = actions
        self.logger = logger
        self.tries = {}

    def _try(self, action, workItem):
        if action.config.name in self.tries:
            self.tries[action.config.name] += 1
        else:
            self.tries[action.config.name] = 1
            
        try:
            action.run(workItem, action.args)
        except Exception, e:
            if action.retries < self.tries:
                self.logger.log("retrying action %s" % action.config.name)
                self._try(action)
            else:
                self.logger.log("action %s has failed with exception %s" % (action.config.name, str(e)))
                raise WatcherTransactionError("action %s has failed with exception: %s" \
                                                % (action.config.name, str(e)))
    def run(self, workItem):
        for a in self.actions:
            self._try(a, workItem)



class ExecutePath(WatcherContainer):
    """the order in which actions are executed
       subclasses will likely want to define their own
       execute method
    """
    implements(IExecutePath)
    def __init__(self, *args, **kwargs):
        super(ExecutePath, self).__init__(*args, **kwargs)
        self.actions = []
        self.finalAction = None
        self.logger = Logger(base="%s_%s" % (self.parent.parent.config.name,
                                            self.parent.config.name))
        for elem in self.config.path.childNodes:
            try:
                if elem.nodeName == "action":
                    action = elem
                    a = Action("action_%s" % action.name, self.configPath, parent=self)
                    for arg in action.arg:
                        a.args[arg.name] = arg.xml_text_content()
                    self.actions.append(a)
                elif elem.nodeName == "decision":
                    if self.finalAction is not None:
                        raise WatcherConfigError("execute path has too many final action declarations!")
                    self.finalAction = Decision('decision_%s' % elem.xml_text_content(), self.configPath, parent=self)
            except AttributeError:
                continue


    def execute(self, workItem, actionSet=None):
        """ here we are able to execute actions in blocks
            and put transactional wrappers around said blocks
        """ 
        localItem = self._openLocalCopy(workItem)
        if not actionSet:
            actionSet = self.actions
        try:
            Transaction(actionSet, self.logger).run(localItem)
        except WatcherTransactionError, e:
            self.logger.log(str(e))
            # here is, in subclasses, where specific handler logic
            # would occur if moving past a failed action is acceptable
            raise
        # if we have set a final action, run it
        # whatever the final action is, close the local copy
        if self.finalAction is not None:
            self.finalAction.run(localItem)
        self._closeLocalCopy(localItem)


    def _openLocalCopy(self, workItem):
        tmpdir = mkdtemp()
        fname = os.path.basename(workItem)
        fromDir = os.path.dirname(workItem)
        newfname = os.path.join(tmpdir, fname)
        try:
            shutil.copyfile(workItem, newfname)
        except IOError:
            raise WatcherActionError("File %s was not able to be copied from %s to %s to make local working copy" % (fname, fromDir, tmpdir))
        return newfname
        

    def _closeLocalCopy(self, localItem):
        d = os.path.dirname(os.path.abspath(localItem))
        try:
            os.unlink(localItem)
        except OSError:
            pass
        os.rmdir(d)


class Branch(WatcherContainer):
    """a watch can contain one or more branches"""
    implements(IBranch)
    def __init__(self, *args, **kwargs):
        """config will be an amara config, parent must be a Watch instance"""
        super(Branch, self).__init__(*args, **kwargs)
        if (not IWatch.providedBy(self.parent)) and (not IDecision.providedBy(self.parent)):
            raise WatcherRuntimeError("Branch must be child of Watch or Decision instance")
        self.logger = Logger(base="%s_%s" % (self.parent.config.name, 
                                             self.config.name))
        self.execute_path = ExecutePath("execute_path_%s" % self.config.execute_path.xml_text_content(), self.configPath, parent=self)
        